﻿//PlazaStream.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Plaza
{
	using System;
	using System.Collections.Generic;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.Plaza.Metadata;
	using StockSharp.Plaza.Wrappers;

	/// <summary>
	/// Тип потока репликации.
	/// </summary>
	public enum PlazaStreamReplicationTypes
	{
		/// <summary>
		/// Данные получаются из локальной БД клиента репликации в режиме снэпшот.
		/// </summary>
		Local = 0,

		/// <summary>
		/// Используются локальные данные плюс данные от сервера репликации в режиме снэпшот. После получения всех данных от сервера поток закрывается.
		/// </summary>
		CombinedSnapshot = 1,

		/// <summary>
		/// Используются локальные данные плюс данные от сервера репликации в режиме снэпшот. После получения всех данных от сервера поток переходит в режим онлайн-репликации.
		/// </summary>
		CombinedDynamic = 2,

		/// <summary>
		/// Данные получаются от сервера репликации в режиме снэпшот. Используется для клиентов репликации, у которых БД не задана.
		/// </summary>
		RemoteSnapshot = 3,

		/// <summary>
		/// Начинает получать данные в режиме онлайн сразу.
		/// </summary>
		RemoteOnline = 8,

		/// <summary>
		/// Флаг, предписывающий сразу удалять из БД клиента записи, помеченные, как удаленные.
		/// </summary>
		RemoveDeleted = 4,
	}

	static class ReplicationSchemas
	{
		// схема по умолчанию
		public const string Current = "CustReplScheme";

		public const string Aggregation = "Aggr";
		public const string FutureCommon = "FutCommon";
		public const string FutureInfo = "FUTINFO";
		public const string FutureOrderBook = "FutOrderBook";
		public const string FutureTrade = "FutTrade";
		public const string FutureDeal = "FutUserDeal";
		public const string Info = "INFO";
		public const string Message = "message";
		public const string MiscInfo = "MiscInfo";
		public const string Money = "MoneyClr";
		public const string OptionCommon = "OptCommon";
		public const string OptionInfo = "OPTINFO";
		public const string OptionOrderBook = "OptOrderBook";
		public const string OptionTrade = "OptTrade";
		public const string OptionDeal = "OptUserDeal";
		public const string Portfolio = "PART";
		public const string Position = "POS";
		public const string Index = "RTSIndex";
		public const string Vm = "VM";
		public const string Volatility = "Volatility";
	}

	/// <summary>
	/// Поток данных сервера Плазы.
	/// </summary>
	public class PlazaStream : Disposable
	{
		private readonly object _initLock = new object();

		internal PlazaStream(string name)
		{
        	if (name.IsEmpty())
        		throw new ArgumentNullException("name");

			Name = name;
			ReplicationSchema = ReplicationSchemas.Current;
		}

		/// <summary>
		/// Название потока.
		/// </summary>
		public string Name { get; private set; }

		internal string ReplicationSchema { get; private set; }

		internal int LifeNum
		{
			get { return NativeStream.TableSet.LifeNum; }
			set { NativeStream.TableSet.LifeNum = value; }
		}

		internal readonly SynchronizedSet<PlazaTable> TableList = new SynchronizedSet<PlazaTable>();

		/// <summary>
		/// Все таблицы, входящие в поток.
		/// </summary>
		public IEnumerable<PlazaTable> Tables
		{
			get { return TableList.ToArray(); }
		}

		private PlazaStreamReplicationTypes _replicationType = PlazaStreamReplicationTypes.CombinedDynamic;

		/// <summary>
		/// Способ получения данных для потока репликации. По-умолчанию равно <see cref="PlazaStreamReplicationTypes.CombinedDynamic"/>.
		/// </summary>
		public PlazaStreamReplicationTypes ReplicationType
		{
			get { return _replicationType; }
			set
			{
				if (_replicationType == value)
					return;

				_replicationType = value;

				lock (_initLock)
				{
					if (_nativeStream != null)
						_nativeStream.Type = value;
				}
			}
		}

		/// <summary>
		/// Название конфигурационного файла, где будут сохранены настройки по потоку.
		/// </summary>
		public string ConfigFileName { get; set; }

		private IStreamWrapper _nativeStream;

		internal IStreamWrapper NativeStream
		{
			get
			{
				if (_nativeStream == null)
					throw new InvalidOperationException("Поток '{0}' не инициализирован.".Put(Name));

				return _nativeStream;
			}
			private set { _nativeStream = value; }
		}

		/// <summary>
		/// Проверить, находится ли поток в состоянии получении онлайн-данных.
		/// </summary>
		/// <returns>
		/// Находится ли поток в состоянии получении онлайн-данных.
		/// </returns>
		public bool IsOnline()
		{
			return NativeStream != null && NativeStream.State == StreamWrapperStates.Online;
		}

		internal void Init()
		{
			lock (_initLock)
			{
				if (_nativeStream != null)
					throw new InvalidOperationException("Поток не был деинициализирован с предыдущего запуска.");

				if (ConfigFileName.IsEmpty())
					throw new InvalidOperationException("Конфигурационный файл не указан.");

				NativeStream = WrapperHelper.CreateStream();
				NativeStream.ConnectionString = string.Empty;
				NativeStream.Type = ReplicationType;
				NativeStream.StreamName = Name;

				NativeStream.StreamDataBegin += OnStreamDataBegin;
				NativeStream.StreamDataEnd += OnStreamDataEnd;
				NativeStream.StreamDatumDeleted += OnStreamDatumDeleted;
				NativeStream.StreamStateChanged += OnStreamStateChanged;
				NativeStream.StreamDataInserted += OnStreamDataInserted;
				NativeStream.StreamDataUpdated += OnStreamDataUpdated;
				NativeStream.StreamDataDeleted += OnStreamDataDeleted;
				NativeStream.StreamLifeNumChanged += OnStreamLifeNumChanged;

				NativeStream.TableSet = WrapperHelper.CreateTableSet();
				NativeStream.TableSet.Init(ConfigFileName, ReplicationSchema);
			}
		}

		internal void CloseNativeStream()
		{
			lock (_initLock)
			{
				NativeStream.StreamDataBegin -= OnStreamDataBegin;
				NativeStream.StreamDataEnd -= OnStreamDataEnd;
				NativeStream.StreamDatumDeleted -= OnStreamDatumDeleted;
				NativeStream.StreamStateChanged -= OnStreamStateChanged;
				NativeStream.StreamDataInserted -= OnStreamDataInserted;
				NativeStream.StreamDataUpdated -= OnStreamDataUpdated;
				NativeStream.StreamDataDeleted -= OnStreamDataDeleted;
				NativeStream.StreamLifeNumChanged -= OnStreamLifeNumChanged;

				if (NativeStream.State != StreamWrapperStates.Closed)
					NativeStream.Close();

				NativeStream = null;
			}
		}

		/// <summary>
		/// Событие сброса состояния всего потока.
		/// </summary>
		internal event Action Flushed;

		/// <summary>
		/// Событие начала обработки данных.
		/// </summary>
		internal event Action Begin;

		/// <summary>
		/// Событие окончания обработки данных.
		/// </summary>
		internal event Action End;

		/// <summary>
		/// Событие удаления данных.
		/// </summary>
		internal event Action<string, long> Cleared;

		/// <summary>
		/// Событие вставки новых данных.
		/// </summary>
		internal event Action<string, IRecordWrapper> Inserted;

		/// <summary>
		/// Событие удаления данных.
		/// </summary>
		internal event Action<string, IRecordWrapper> Deleted;

		/// <summary>
		/// Событие о записи в лог.
		/// </summary>
		public event Action<PlazaStream, string> Log;

		private void OnStreamDatumDeleted(IStreamWrapper stream, string tableName, long revision)
		{
			// P2ClientGate.doc - стр. 18:
			// Специальное уведомление StreamDatumDeleted присылается сервером по каждой таблице в начале синхронизации. 
			// Это уведомление означает «данных с ревиженами меньше указанного, на сервере нет».
			// «Безбазовый» клиент должен удалить все данные с ревиженами, меньшими указанного в уведомлении из своего хранилища.
			RaiseDatumDeleted(tableName, revision);
		}

		private void OnStreamDataBegin(IStreamWrapper stream)
		{
			RaiseDataBegin();
		}

		private void OnStreamDataEnd(IStreamWrapper stream)
		{
			RaiseDataEnd();
		}

		private void OnStreamStateChanged(IStreamWrapper stream, StreamWrapperStates newState)
		{
			Log.SafeInvoke(this, "StreamStateChanged: State {0}".Put(newState));
			//StateChanged.SafeInvoke();
		}

		private void OnStreamLifeNumChanged(IStreamWrapper stream, int lifeNum)
		{
			Log.SafeInvoke(this, "StreamLifeNumChanged: LifeNum {0}".Put(lifeNum));

			// P2ClientGate.doc стр. 18:
			// При получении этого уведомления «базовый» клиент очищает базу, изменяет номер жизни во внутренних структурах так, 
			// чтобы он соответствовал серверному и переоткрывает поток (переходит в состояние ReOpen). 
			// ВАЖНО! При получении этого уведомления клиентский код должен вызвать P2TableSet-> SetLifeNumToIni 
			// для сохранения номера жизни в ini-файле схемы!
			//NativeStream.TableSet.LifeNum = lifeNum;
			//NativeStream.TableSet.UpdateLifeNumber(ConfigFileName);

			LifeNum = lifeNum;
			RaiseFlushed();
		}

		private void OnStreamDataInserted(IStreamWrapper stream, string tableName, IRecordWrapper record)
		{
			RaiseDataInserted(tableName, record);
		}

		private void OnStreamDataUpdated(IStreamWrapper stream, string tableName, long id, IRecordWrapper record)
		{
			// только для базового клиента
		}

		private void OnStreamDataDeleted(IStreamWrapper stream, string tableName, long id, IRecordWrapper record)
		{
			RaiseDataDeleted(tableName, record);
		}

		/// <summary>
		/// Вызвать событие <see cref="Begin"/>.
		/// </summary>
		private void RaiseDataBegin()
		{
			Begin.SafeInvoke();
		}

		/// <summary>
		/// Вызвать событие <see cref="End"/>.
		/// </summary>
		private void RaiseDataEnd()
		{
			End.SafeInvoke();
		}

		/// <summary>
		/// Вызвать событие <see cref="Flushed"/>.
		/// </summary>
		private void RaiseFlushed()
		{
			Flushed.SafeInvoke();
		}

		/// <summary>
		/// Вызвать событие <see cref="Cleared"/>.
		/// </summary>
		/// <param name="tableName">Название таблицы.</param>
		/// <param name="revision">Номер ревизии.</param>
		private void RaiseDatumDeleted(string tableName, long revision)
		{
			Cleared.SafeInvoke(tableName, revision);
		}

		/// <summary>
		/// Вызвать событие <see cref="Inserted"/>.
		/// </summary>
		/// <param name="tableName">Название таблицы.</param>
		/// <param name="record">Запись.</param>
		private void RaiseDataInserted(string tableName, IRecordWrapper record)
		{
			Inserted.SafeInvoke(tableName, record);
		}

		/// <summary>
		/// Вызвать событие <see cref="Deleted"/>.
		/// </summary>
		/// <param name="tableName">Название таблицы.</param>
		/// <param name="record">Запись.</param>
		private void RaiseDataDeleted(string tableName, IRecordWrapper record)
		{
			Deleted.SafeInvoke(tableName, record);
		}

		internal void Close()
		{
			if (NativeStream.State == StreamWrapperStates.Closed)
				return;

			NativeStream.Close();
			//State = PlazaStreamStates.Closed;
		}

		internal void RaiseLog(string message)
		{
			Log.SafeInvoke(this, message);
		}

		/// <summary>
		/// Освободить занятые ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			if (NativeStream == null)
				return;

			CloseNativeStream();
		}

		/// <summary>
		/// Получить строковое представление.
		/// </summary>
		/// <returns>Строковое представление.</returns>
		public override string ToString()
		{
			return Name;
		}
	}
}